package com.axdac.mixbind;


import android.os.Handler;
import android.os.Message;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 *
 * the class for control thread switch
 * by lianghailun
 * Created by lianghailun on 2017/6/20 0020.
 */
public class TimeLine {
    private List<RunningTask> runningTasks;
    private CountDownLatch beforeLatch; // make sure all threads run with main at the same time
    private CountDownLatch afterLatch;  // this latch is to divide level
    private HashMap<String,Object> map;  // the object to deliver msg in all functions
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    public void putMap(String key,Object obj){
        map.put(key,obj);
    }

    public static class Builder{
        private List<Object> objects ;
        public TimeLine timeLine;
        public Builder(){
            objects = new ArrayList<Object>();
            timeLine = new TimeLine();
        }

        public Builder put(String key,Object obj){
            timeLine.putMap(key,obj);
            return this;
        }

        public Builder addlib(Object obj){
            objects.add(obj);
            return this;
        }

        public TimeLine run(){
            timeLine.startSteinsGate(objects);
            return timeLine;
        }

    }

    private TimeLine(){
        map = new HashMap<String,Object>();
        runningTasks = new ArrayList<RunningTask>();
    }

    /**
     * run the objects with methods
     */
    public  void  startSteinsGate(List<Object> objs){
        runningTasks.clear();//clear all task
        for (Object obj : objs) {
            trackTask(obj, obj.getClass());//parse each obj thread
        }
        startRun();//start run
    }

    /**
     * start run
     */
    private void startRun() {
        new Thread(){
            @Override
            public void run() {//schdlue all in one thread
                super.run();
                try {

                    for (final RunningTask task : runningTasks) {
                        beforeLatch = new CountDownLatch(1);//set the lautch make sure all run in same time
                        afterLatch = new CountDownLatch(1+task.backgrounds.size());// count down all thread in a level
                        if(task.backgrounds.size()!=0) {// make ready for background thread
                            for (Method m : task.backgrounds) {
                                new MyThread(task.obj, m).start();
                            }
                        }
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                beforeLatch.countDown();// swich  to main notify background thread to run
                                for(Method m : task.forgrounds){// run the method in main thread
                                    try {
                                        RunningMsg msg = new RunningMsg();
                                        msg.map = map;
                                        msg.currentTime = System.currentTimeMillis();
                                        m.invoke(task.obj,msg);
                                    } catch (IllegalAccessException e) {
                                        e.printStackTrace();
                                    } catch (InvocationTargetException e) {
                                        e.printStackTrace();
                                    }
                                }
                                afterLatch.countDown();// countdown main
                            }
                        });
                        try {
                            afterLatch.await();// wait for a level to finish
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private class MyThread extends Thread{
        private Object obj;
        private Method m;

        public MyThread(Object obj, Method m) {
            this.obj = obj;
            this.m = m;
        }

        @Override
        public void run() {
            super.run();
            try {
                beforeLatch.await();// wait for mainthread
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                RunningMsg msg = new RunningMsg();
                msg.map = map;
                msg.currentTime = System.currentTimeMillis();
                m.invoke(obj,msg);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            afterLatch.countDown();// countdown for a level
        }
    }

    /**
     * bind method in a object
     * @param obj
     * @param cl
     */
    public  void trackTask( Object obj,Class<?> cl) {
        for (Method m : cl.getDeclaredMethods()) {
            TimeLineTick tick = m.getAnnotation(TimeLineTick.class);
            if (tick != null) {
                System.out.println("Level:" + tick.level() + "thread"
                        + tick.thread());
                boolean isArealy = false;
                for(RunningTask runningTask : runningTasks){
                    if(runningTask.level == tick.level()){
                        isArealy = true;
                        if(tick.thread()== TimeLineTick.FORGROUND){
                            runningTask.forgrounds.add(m);
                        }else {
                            runningTask.backgrounds.add(m);
                        }
                    }
                }
                if(isArealy){
                    continue;
                }else{
                    RunningTask runningTask = new RunningTask();
                    runningTask.level = tick.level();
                    runningTask.backgrounds = new ArrayList<Method>();
                    runningTask.forgrounds = new ArrayList<Method>();
                    runningTask.obj = obj;
                    if(tick.thread()== TimeLineTick.FORGROUND){
                        runningTask.forgrounds.add(m);
                    }else {
                        runningTask.backgrounds.add(m);
                    }
                    runningTasks.add(runningTask);
                }
            }
        }
        Collections.sort(runningTasks,new LevelComparator());
    }

    /**
     * sort for level
     */
    private class  LevelComparator implements Comparator{


        @Override
        public int compare(Object o1, Object o2) {
            RunningTask t1= (RunningTask) o1;
            RunningTask t2= (RunningTask) o2;
            return t1.level-t2.level;
        }
    }

    /**
     * model for running
     */
    private class RunningTask{
        public int level = 0;
        public Object obj;
        public List<Method> backgrounds;
        public List<Method> forgrounds;
    }

    /**
     * Created by Administrator on 2017/6/17 0017.
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface TimeLineTick {
        public final static String FORGROUND = "forground";
        public final static String BACKGROUND = "background";

        public int level() default 1;
        public String thread() default "forground";
    }

    public class RunningMsg{
        public HashMap<String,Object> map;
        public long currentTime;
    }

    public interface timeLinePackage{

    }
}

